#ifndef __ZEN_DEBUG__
#define __ZEN_DEBUG__
#pragma once

	#include "defines.hpp"

	// If this macro is defined the logging will be printed in the standard output (std::out).
	#define ZEN_DEBUG_CONSOLE

	// If this macro is defined with a file name value the logging will be printed into that file.
	// Warning: logging into a file is slow.
	//#define ZEN_DEBUG_FILE "log.txt"

	// If this macro is defined as an rvalue returning an std::ostream instance the logging will be printed into that stream.
	//#define ZEN_DEBUG_CUSTOM std::cout


    #ifdef ZEN_DEBUG


        #include <cstdio>
        #include <vector>
        #include <exception>
		#include <iostream>
		#include <string>
		
		namespace zen {

			namespace debug {

				class Callstack {

				public:

					Callstack(std::string function);
					~Callstack();

					static void log(std::string message);


				private:

					enum WritePhase {
						Start = 0,
						Continue = 1,
						Finish = 2
					};

					static std::vector<std::string> cstack;
					static std::string lastLog;

					Callstack();
					Callstack(const Callstack&);
					Callstack& operator = (const Callstack&);

					#ifdef ZEN_DEBUG_FILE
					static FILE* logFile;
					#endif

					static void out(std::string log, WritePhase phase);

					static void print_callstack();

				};
				

			} // namespace zen_debug

		} // namespace zen_debug
		

		#define zen_call(function)					zen::debug::Callstack callstack(function)
        #define zen_log(message)					zen::debug::Callstack::log(message)
        #define zen_else_log(message)				else zen::debug::Callstack::log(message)
        #define zen_if_log(expr, message)			if (expr) zen::debug::Callstack::log(message)
        #define zen_else_if_log(expr, message)		else if (expr) zen::debug::Callstack::log(message)

        #define zen_log_mat4(m) { \
                zen_log(to_str(m[0][0]) + ", " + to_str(m[0][1]) + ", " + to_str(m[0][2]) + ", " + to_str(m[0][3]) + ",");\
                zen_log(to_str(m[1][0]) + ", " + to_str(m[1][1]) + ", " + to_str(m[1][2]) + ", " + to_str(m[1][3]) + ",");\
                zen_log(to_str(m[2][0]) + ", " + to_str(m[2][1]) + ", " + to_str(m[2][2]) + ", " + to_str(m[2][3]) + ",");\
                zen_log(to_str(m[3][0]) + ", " + to_str(m[3][1]) + ", " + to_str(m[3][2]) + ", " + to_str(m[3][3]) + "\n");\
            }
		#define zen_log_vec2(v) {zen_log(to_str(v[0]) + ", " + to_str(v[1]) + ")");}
		#define zen_log_vec3(v) {zen_log(to_str(v[0]) + ", " + to_str(v[1]) + ", " + to_str(v[2]) + ")");}
		#define zen_log_vec4(v) {zen_log(to_str(v[0]) + ", " + to_str(v[1]) + ", " + to_str(v[2]) + ", " + to_str(v[3]) + ")");}
		
		#define fail_assert(message)					throw new exception(std::string("Assertion failed in file ") + __FILE__ + " at line " + __LINE__ + " with message: " + message)
		#define assert_true(message, value)             if ((value) != true) { fail_assert(message); }
		#define assert_false(message, value)            if ((value) != false) { fail_assert(message); }
		#define assert_equal(message, value1, value2)   if ((value1) != (value2)) { fail_assert(message); }


	#else


        #define zen_call(function)
        #define zen_log(message)
        #define zen_else_log(message)
        #define zen_if_log(expr, message)
        #define zen_else_if_log(expr, message)

        #define zen_log_mat4(m)
		#define zen_log_vec2(v)
        #define zen_log_vec3(v)
		#define zen_log_vec4(v)

        #define assert_true(message, value)
        #define assert_false(message, value)
        #define assert_equal(message, value1, value2)


    #endif

#endif
